package it.webrules.deploymanager.core.dy.deploy.tomcat;

import it.webrules.deploymanager.core.dy.ApplicationServerConnection;
import it.webrules.deploymanager.core.dy.deploy.AvailableServerException;
import it.webrules.deploymanager.core.dy.deploy.DeployException;
import it.webrules.deploymanager.core.dy.deploy.MBeanManager;
import it.webrules.deploymanager.core.dy.jmxutil.SocketFactoryLessTimeout;
import it.webrules.deploymanager.core.hook.HandleHook;
import it.webrules.xsd.dmc.config.v1.AjpMode;
import it.webrules.xsd.dmc.config.v1.GeneralConfig;
import it.webrules.xsd.dmc.config.v1.Hook;
import it.webrules.xsd.dmc.config.v1.WebappConfig.Deploys;
import it.webrules.xsd.dmc.config.v1.WebappConfig.Deploys.Tomcat;

import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import javax.management.MalformedObjectNameException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.jmx.remote.util.EnvHelp;

/**
 * 
 * Crea la connessione al Tomcat 
 * 
 * @author dometec
 *
 */
@SuppressWarnings("restriction")
public class TomcatConnection implements ApplicationServerConnection {

	private static final int ONESEC_IN_MS = 1000;

	private static final int CONNECTION_TIMEOUT = 1000;

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	private Map<String, Object> jmxConnectorEnv;
	private JMXConnector jmxConnector;
	private Tomcat server;
	private InputStream artifact;
	private TomcatMBeanManager mbeanManager;

	private final HandleHook handleHook;
	private final TomcatManager tomcatManager;
	private final GeneralConfig generalConfig;
	private final Deploys deploys;

	public TomcatConnection(Tomcat server, Deploys deploys, GeneralConfig generalConfig) throws AvailableServerException {

		this.server = server;
		this.deploys = deploys;
		this.generalConfig = generalConfig;

		jmxConnectorEnv = new HashMap<String, Object>();
		jmxConnectorEnv.put(EnvHelp.SERVER_CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);
		jmxConnectorEnv.put(EnvHelp.FETCH_TIMEOUT, CONNECTION_TIMEOUT);
		jmxConnectorEnv.put(EnvHelp.CLIENT_CONNECTION_CHECK_PERIOD, 0);
		jmxConnectorEnv.put("jmx.remote.rmi.client.socket.factory", SocketFactoryLessTimeout.class);
		jmxConnectorEnv.put("rmi.client.socket.factory", SocketFactoryLessTimeout.class);

		try {

			handleHook = new HandleHook();

			String version = server.getVersion();
			String name = server.getName();
			String manValue = server.getManager().getValue();
			String manUsername = server.getManager().getUsername();
			String manPassword = server.getManager().getPassword();

			tomcatManager = new TomcatManager(version, name, manValue, manUsername, manPassword);

		} catch (URISyntaxException e) {
			throw new AvailableServerException("Error on manager tomcat's URL  (" + server.getName() + ") ", e);
		}
	}

	public TomcatConnection(Tomcat server, Deploys deploys, GeneralConfig generalConfig, InputStream artifact)
			throws AvailableServerException {
		this(server, deploys, generalConfig);
		this.artifact = artifact;
	}

	@Override
	public void connect() throws DeployException {

		logger.info("Connecting to {}...", server.getName());

		try {

			// TODO gestione username e password
			JMXServiceURL serviceURL = new JMXServiceURL(server.getJmx().getValue());
			jmxConnector = JMXConnectorFactory.newJMXConnector(serviceURL, jmxConnectorEnv);
			jmxConnector.connect();
			mbeanManager = new TomcatMBeanManager(jmxConnector, server.getEnginename());

		} catch (Exception e) {
			throw new DeployException("Error connection to server via jmx (" + server.getName() + ") ", e);
		}
	}

	@Override
	public void checkConnect() throws DeployException {

		try {
			jmxConnector.getMBeanServerConnection().getMBeanCount();
		} catch (Exception e) {
			// try to reconnect
			connect();
		}

	}

	@Override
	public void disconnect() throws DeployException {
		try {
			jmxConnector.close();
		} catch (Exception e) {
			throw new DeployException("Error disconnecting from server via jmx (" + server.getName() + ") ", e);
		}
	}

	@Override
	public MBeanManager getMBeanManager() {
		return mbeanManager;
	}

	@Override
	public int isAvailability(StringBuilder errors) {

		int error = 0;

		try {
			connect();
		} catch (Exception e) {
			error++;
			errors.append("Can't connect to server via jmx (" + server.getName() + "): " + e.getMessage());
		}

		error += tomcatManager.isAvailability(errors);

		return error;
	}

	@Override
	public void deploy() throws DeployException {

		try {

			connect();

			beforestopHook();

			if (server.getJmx().getAjpmode().equals(AjpMode.PAUSE))
				mbeanManager.pauseAJP();
			else
				mbeanManager.stopAJP();

			mbeanManager.waitForProcessingAllRequest(deploys.getProcessingtimeout());

			beforeundeployHook();

			if (mbeanManager.isDeployed(server.getVirtualhost(), server.getContextname())) {
				tomcatManager.undeploy(server.getContextname());
				mbeanManager.waidUndeploy(server.getVirtualhost(), server.getContextname(), deploys.getUndeploytimeout());
			}

			beforedeployHook();

			tomcatManager.deploy(server.getContextname(), artifact);

			waitDeploy();

			beforestartHook();

			if (server.getJmx().getAjpmode().equals(AjpMode.PAUSE))
				mbeanManager.resumeAJP();
			else
				mbeanManager.startAJP();

			afterstartHook();

		} catch (DeployException e) {
			throw e;

		} catch (Exception e) {
			throw new DeployException("Error deploying artifact on tomcat (" + server.getName() + ") ", e);
		}
	}

	@Override
	public void waitDeploy() throws MalformedObjectNameException, NullPointerException, IOException, InterruptedException, DeployException {
		mbeanManager.waidDeploy(server.getVirtualhost(), server.getContextname(), deploys.getDeploytimeout());
	}

	/**
	 * Check availability of tomcat. Check if http port is open
	 * 
	 *  @param sec second before timed out
	 * 
	 */
	@Override
	public void waitStart() {

		int sec = deploys.getDeploytimeout();
		while (sec > 0) {

			if (tomcatManager.isPortAvailable()) {
				try {
					connect();
					logger.info("Started.");
					return;
				} catch (DeployException e) {
					logger.trace("JMX NOT Available.");
				}
			}

			sec--;

			try {
				Thread.sleep(ONESEC_IN_MS);
			} catch (InterruptedException e) {
				logger.error("Error waiting start.", e);
			}
		}

		logger.error("Timed out while waiting tomcat starting!");

	}

	private void afterstartHook() throws DeployException {
		Hook afterstart = server.getAfterstart();
		if (afterstart != null) {
			logger.debug("Elaborate afterstart hook.");
			handleHook.manageHook(generalConfig, this, afterstart);
			checkConnect();
		}
	}

	private void beforestartHook() throws DeployException {
		Hook beforestart = server.getBeforestart();
		if (beforestart != null) {
			logger.debug("Elaborate beforestart hook.");
			handleHook.manageHook(generalConfig, this, beforestart);
			checkConnect();
		}
	}

	private void beforedeployHook() throws DeployException {
		Hook beforedeploy = server.getBeforedeploy();
		if (beforedeploy != null) {
			logger.debug("Elaborate beforedeploy hook.");
			handleHook.manageHook(generalConfig, this, beforedeploy);
			checkConnect();
		}
	}

	private void beforeundeployHook() throws DeployException {
		Hook beforeundeploy = server.getBeforeundeploy();
		if (beforeundeploy != null) {
			logger.debug("Elaborate beforeundeploy hook.");
			handleHook.manageHook(generalConfig, this, beforeundeploy);
			checkConnect();
		}
	}

	private void beforestopHook() throws DeployException {
		Hook beforestop = server.getBeforestop();
		if (beforestop != null) {
			logger.debug("Elaborate beforestop hook.");
			handleHook.manageHook(generalConfig, this, beforestop);
			checkConnect();
		}
	}

}